package com.hfi.util;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author ChangLiang
 * @date 2019/9/30
 */
public class ClassUtil {

    /**
     * 获取指定包(包括子包)中指定注解的所有类并返回
     * 其只能获取类上的注解
     * 注意 isAnnotationPresent方法 注解
     * @param packageName 包名
     * @param annotationClass 注解名
     * @return
     */
    public static List<Class<?>> getClassListByAnnotation(String packageName,
                                                           Class<? extends Annotation> annotationClass) {
        List<Class<?>> classList = new ArrayList<>();
        try {
            Enumeration<URL> urls =
                    Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (Objects.nonNull(url)) {
                    String protocol = url.getProtocol();
                    if (StringUtils.equals(protocol,"file")) {
                        String packagePath = url.getPath();
                        addClassByAnnotation(classList,packagePath,packageName,annotationClass);
                    } else if (StringUtils.equals(protocol, "jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (StringUtils.endsWith(jarEntryName,".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/",
                                        ".");
                                Class<?> cls = Class.forName(className);
                                if (cls.isAnnotationPresent(annotationClass)) {
                                    classList.add(cls);
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return classList;
    }

    private static void addClassByAnnotation(List<Class<?>> classList, String packagePath, String packageName, Class<
            ? extends Annotation> annotationClass) {
        try {
            File[] files = getClassFiles(packagePath);
            if (Objects.nonNull(files)) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        Class<?> cls = Class.forName(className);
                        if (cls.isAnnotationPresent(annotationClass)) {
                            classList.add(cls);
                        }
                    } else {
                        String subPackagePath = getSubPackagePath(packagePath, fileName);
                        String subPackageName = getSubPackageName(packageName, fileName);
                        // 递归
                        addClassByAnnotation(classList,subPackagePath,subPackageName,annotationClass);
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static File[] getClassFiles(String packagePath) {
        return new File(packagePath).listFiles(pathname -> (pathname.isFile() && StringUtils.endsWith(pathname.getName(),".class")) || pathname.isDirectory());
    }

    private static String getClassName(String packageName, String fileName) {
        String className = fileName.substring(0, fileName.lastIndexOf("."));
        if (StringUtils.isNotEmpty(className)) {
            className = packageName + "." + className;
        }
        return className;
    }

    private static String getSubPackagePath(String packagePath, String filePath) {
        String subPackagePath = filePath;
        if (StringUtils.isNotEmpty(subPackagePath)) {
            subPackagePath = packagePath + "/" + subPackagePath;
        }
        return subPackagePath;
    }

    private static String getSubPackageName(String packageName, String filePath) {
        String subPackageName = filePath;
        if (StringUtils.isNotEmpty(subPackageName)) {
            subPackageName = packageName + "." + subPackageName;
        }
        return subPackageName;
    }
}
